/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.web30.idm.openldap;

import com.novell.ldap.LDAPAttribute;
import com.novell.ldap.LDAPConnection;
import com.novell.ldap.LDAPConstraints;
import com.novell.ldap.LDAPEntry;
import com.novell.ldap.LDAPException;
import com.novell.ldap.LDAPModification;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.web30.idm.model.IDMException;
import org.web30.idm.model.Web30Person;
import org.web30.idm.service.AccountService;

/**
 * This class delegate operation to OpenLDAP Server
 * Root DSE: dc=web30,dc=org
 * Users under subtree: dc=organization-name,dc=web30,dc=org
 * Privilege Group under subtree: ou=Groups, dc=web30,dc=org
 * Privllege Control group for specific organization: cn=organizaiton-name,ou=Groups, dc=web30,dc=org
 * @author Xia Zhenyu
 */
public class AccountServiceOpenLDAP implements AccountService {

    /**
     * LDAP connection
     */
    private LDAPConnection conn = null;
    private String hostName = Web30Constants.LDAP_DEFAULT_HOST;
    private int port = LDAPConnection.DEFAULT_PORT;
    private String bindDN = Web30Constants.LDAP_DEFAULT_USER;
    private String pwd = Web30Constants.LDAP_DEFAULT_PWD;

    /**
     * Default construtor. using localhost:389 cn=admin,dc=web30,dc=org,pwd:eds321
     */
    public AccountServiceOpenLDAP() {
    }

    /**
     * Construtor
     * @param hostname
     * @param port
     * @param default_dn
     * @param default_password
     */
    public AccountServiceOpenLDAP(String hostname,
            int port,
            String default_dn,
            String default_password) {
        if (hostname != null) {
            this.hostName = hostname;
        }
        if (port != 0) {
            this.port = port;
        }
        if (default_dn != null) {
            this.bindDN = default_dn;
        }
        if (default_password != null) {
            this.pwd = default_password;
        }
    }

    /**
     * Add users into identity repository
     * @param users list of user. users as instance of Web30InetOrgPerson for OpenLDAP
     * @throws IDMException add entry already exists will be ignored
     */
    public void addUsers(List users) throws IDMException {
        if (users == null) {
            return;
        }
        try {
            this.ldapInit();
            for (int i = 0; i < users.size(); i++) {
                Web30InetOrgPerson user = (Web30InetOrgPerson) users.get(i);
                try {
                    this.addEntry(user.getLDAPEntry());
                } catch (Exception e) {
                    if (e instanceof LDAPException &&
                            ((LDAPException) e).getResultCode() == LDAPException.ENTRY_ALREADY_EXISTS) {
                        continue;
                    }
                }
            }
            this.ldapClose();
        } catch (Exception e) {
            if (e instanceof LDAPException) {
                throw new IDMException(e);
            }
        }
    }

    /**
     * Reset password of specific person
     * @param p Instance of Web30InetOrgPerson for OpenLDAP
     * @param newPassword. new password
     * @throws IDMException
     */
    public void resetPassword(Web30Person p, String newPassword, String oldPassword)
            throws IDMException {
        
        if (p == null) {
            throw new IDMException(IDMException.IDM_INVALID_PASSWORD);
        }
        if (p.getIdentity() == null) {
            throw new IDMException(IDMException.IDM_INVALID_PASSWORD);
        }
        if (p.getOrganization() == null) {
            throw new IDMException(IDMException.IDM_INVALID_PASSWORD);
        }
        if (p instanceof Web30InetOrgPerson) {
            try {
                this.ldapInit();
                this.setPassword(((Web30InetOrgPerson) p).getDN(), newPassword, oldPassword);
                this.ldapClose();
            } catch (Exception ex) {
                throw new IDMException(ex);
            }
        } else {
            throw new IDMException(IDMException.IDM_INVALID_PASSWORD);
        }
        return;
    }

    /**
     * Delete account from repository
     * @param p
     * @throws IDMException
     */
    public void deleteAccount(Web30Person p) throws IDMException {
        if (p == null) {
            return;
        }
        if (!(p instanceof Web30InetOrgPerson)) {
            return;
        }
        try {
            this.ldapInit();
            this.delEntry(((Web30InetOrgPerson) p).getDN());
            this.ldapClose();
        } catch (Exception e) {
            if (e instanceof LDAPException) {
                LDAPException temp = (LDAPException) e;
                if (temp.getResultCode() != LDAPException.NO_SUCH_OBJECT) {
                    throw new IDMException(e);
                }
            }
        }
    }

    /**
     * Intialize LDAP connection
     * @throws LDAPException
     */
    private void ldapInit() throws LDAPException {

        int ldapVersion = LDAPConnection.LDAP_V3;
        this.conn = new LDAPConnection();
        // connect to the server
        this.conn.connect(this.hostName, this.port);
        LDAPConstraints cons = this.conn.getConstraints();
        cons.setTimeLimit(5000);
        this.conn.setConstraints(cons);
        try {
            // authenticate to the server
            this.conn.bind(ldapVersion, this.bindDN,
                    this.pwd.getBytes("UTF8"));
        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(AccountServiceOpenLDAP.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Add entry in LDAP repository
     * @param entry
     * @throws LDAPException
     */
    private void addEntry(LDAPEntry entry) throws LDAPException {
        if (entry == null) {
            return;
        }
        if (this.conn == null) {
            return;
        }
        this.conn.add(entry);
    }

    /**
     * Delete entry in LDAP repository
     * @param DN
     * @throws LDAPException
     */
    private void delEntry(String DN) throws LDAPException {
        if (this.conn == null) {
            return;
        }
        this.conn.delete(DN);

    }

    /**
     * Authorize User to access resources of organization
     * @param user user entry
     * @param org  organization name
     * @param active  if true grant privilege, otherwise delete privilege
     * @throws LDAPException
     */
    private void authorizeUser(LDAPEntry user, String org, boolean active)
            throws LDAPException {
        if (user == null) {
            return;
        }
        String userDN = user.getDN();
        LDAPAttribute member = new LDAPAttribute(
                Web30Constants.LDAP_SCHEMA_UNIQUEMEMBER, userDN);

        LDAPModification[] modGroup = new LDAPModification[1];
        if (active == true) {
            modGroup[0] = new LDAPModification(LDAPModification.ADD, member);
        } else {
            modGroup[0] = new LDAPModification(LDAPModification.DELETE, member);
        }

        this.conn.modify(this.getGroupDN(org), modGroup);

    }

    /**
     * Get DN represents organztion. such as cn=nec-as,ou=groups,dc=web30,dc=org
     * 1. nec-as is organization name
     * 2. subtree: ou=groups, dc=web30,dc=org is subtree for privilige control group
     * 3. dc=web30, dc=org is Root DSE
     * @param org
     * @return
     */
    private String getGroupDN(String org) {
        return new String("cn=" + org + ",ou=groups,dc=web30,dc=org");
    }

    /**
     * Reset password of specific user
     * @param dn
     * @param password
     * @throws LDAPException
     */
    private void setPassword(String dn, String password, String oldPassword) throws LDAPException {
        LDAPModification[] modifications = new LDAPModification[2];

        LDAPAttribute newPassword = new LDAPAttribute(
                Web30Constants.LDAP_SCHAME_PASSWORD,
                password);

        LDAPAttribute original = new LDAPAttribute(
                Web30Constants.LDAP_SCHAME_PASSWORD,
                oldPassword);

        modifications[0] = new LDAPModification(LDAPModification.DELETE, original);
        modifications[1] = new LDAPModification(LDAPModification.ADD, newPassword);

        this.conn.modify(dn, modifications);
    }

    /**
     * Close connection of LDAP
     * @throws LDAPException
     */
    private void ldapClose() throws LDAPException {
        if (this.conn != null) {
            this.conn.disconnect();
            this.conn = null;
        }

    }

    /**
     * Add user into identity repository
     * @param p Instance of Web30InetOrgPerson for OpenLDAP
     * @throws IDMException
     */
    public void addUser(Web30Person p) throws IDMException {
        if (p == null) {
            return;
        }
        if (!(p instanceof Web30InetOrgPerson)) {
            return;
        }
        try {
            this.ldapInit();
            this.addEntry(((Web30InetOrgPerson) p).getLDAPEntry());
            this.ldapClose();
        } catch (Exception e) {
            if (e instanceof LDAPException) {
                throw new IDMException(e, IDMException.IDM_ADDING_ACCOUNT_EXISTED);
            } else {
                throw new IDMException(e);
            }
        }
    }

    /**
     * Active Users' privilege to web application
     * @param p Web30Person
     * @param orgName  organization name. it's RDN in DIT tree.
     * @throws IDMException
     */
    public void activeUser(Web30Person p, String orgName) throws IDMException {
        if (p == null) {
            return;
        }
        if (!(p instanceof Web30InetOrgPerson)) {
            return;
        }
        try {
            this.ldapInit();
            this.authorizeUser(((Web30InetOrgPerson) p).getLDAPEntry(), orgName, true);
            this.ldapClose();
        } catch (Exception e) {
            if (e instanceof LDAPException) {
                LDAPException temp = (LDAPException) e;
                if (temp.getResultCode() != LDAPException.NO_SUCH_ATTRIBUTE) {
                    throw new IDMException(e);
                }
            }
        }
    }

    /**
     * Delete user from authorizated group
     * @param p specific user
     * @param orgName  organization name. it's RDN in DIT tree.
     * @throws IDMException
     */
    public void disableUser(Web30Person p, String orgName)
            throws IDMException {
        if (p == null) {
            return;
        }
        if (!(p instanceof Web30InetOrgPerson)) {
            return;
        }
        try {
            this.ldapInit();
            this.authorizeUser(((Web30InetOrgPerson) p).getLDAPEntry(), orgName, false);
            this.ldapClose();
        } catch (Exception e) {
            if (e instanceof LDAPException) {
                LDAPException temp = (LDAPException) e;
                if (temp.getResultCode() != LDAPException.NO_SUCH_ATTRIBUTE) {
                    throw new IDMException(e);
                }
            }
        }
    }
}
